/*
* GeoBatch - Open Source geospatial batch processing system
* http://geobatch.geo-solutions.it/
* Copyright (C) 2007-2012 GeoSolutions S.A.S.
* http://www.geo-solutions.it
*
* GPLv3 + Classpath exception
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.geosolutions.geobatch.camel;
import it.geosolutions.filesystemmonitor.monitor.FileSystemEvent;
import it.geosolutions.filesystemmonitor.monitor.FileSystemEventType;
import it.geosolutions.geobatch.camel.beans.JMSFlowRequest;
import it.geosolutions.geobatch.camel.beans.JMSFlowResponse;
import it.geosolutions.geobatch.camel.beans.JMSFlowStatus;
import it.geosolutions.geobatch.catalog.Catalog;
import it.geosolutions.geobatch.catalog.file.DataDirHandler;
import it.geosolutions.geobatch.configuration.event.consumer.file.FileBasedEventConsumerConfiguration;
import it.geosolutions.geobatch.configuration.event.generator.file.FileBasedEventGeneratorConfiguration;
import it.geosolutions.geobatch.flow.file.FileBasedFlowManager;
import it.geosolutions.geobatch.global.CatalogHolder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Future;
import javax.annotation.Resource;
import org.apache.camel.AsyncCallback;
import org.apache.camel.AsyncProcessor;
import org.apache.camel.Exchange;
import org.apache.camel.InOut;
import org.apache.camel.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
*
* @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
*
*/
@ManagedResource
public class JMSFlowManager implements AsyncProcessor {
private final static Logger LOGGER = LoggerFactory.getLogger(JMSFlowManager.class.toString());
private FileBasedEventConsumerConfiguration configuration;
private FileBasedFlowManager parent;
private GBFileSystemEventConsumer consumer;
@Resource(name="dataDirHandler")
private DataDirHandler dataDirHandler;
private void init(String FlowManagerID) throws Exception {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT catalog");
}
Catalog catalog = CatalogHolder.getCatalog();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT parent flow manager");
}
parent = catalog.getResource(FlowManagerID,
it.geosolutions.geobatch.flow.file.FileBasedFlowManager.class);
if (parent == null)
throw new IllegalArgumentException("JMSFlowManager: The flow id \'" + FlowManagerID
+ "\' do not exists into catalog... -> parent == null");
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT configuration");
}
configuration = ((FileBasedEventConsumerConfiguration) parent.getConfiguration()
.getEventConsumerConfiguration()).clone();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT consumer");
}
File configDir=parent.initConfigDir(parent.getConfiguration(), dataDirHandler.getBaseConfigDirectory());
File tempDir=parent.initTempDir(parent.getConfiguration(), dataDirHandler.getBaseTempDirectory());
consumer = new GBFileSystemEventConsumer(configuration, configDir, tempDir);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT injecting consumer to the parent flow");
}
if (parent.addConsumer(consumer)){
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Consumer started: "+consumer);
}
} else {
throw new Exception("Unable to add the consumer to the queue");
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: INIT concluded");
}
}
/**
* try to match the message to the configuration building the list of FileSystemEvent
*
* @param files
* @return the list of FileSystemEvent(s) or null (if the argument files list is null)
*/
private List<FileSystemEvent> buildEventList(List<String> files)
throws IllegalArgumentException {
if (files == null)
return null;
List<FileSystemEvent> list = new ArrayList<FileSystemEvent>();
for (String file : files) {
File theFile = new File(file);
// if not exists or not readable throw exception
if (!theFile.exists() || !theFile.canRead())
throw new IllegalArgumentException("JMSFlowManager: The file \"" + theFile
+ "\" not exists or is not readable.");
// get the right event from the generator configuration
FileSystemEventType ev = ((FileBasedEventGeneratorConfiguration) parent
.getConfiguration().getEventGeneratorConfiguration()).getEventType();
// add the event to the list
list.add(new FileSystemEvent(theFile, ev));
}
return list;
}
private JMSFlowResponse workOn(JMSFlowRequest request) throws Exception {
try {
// initializing members
init(request.getFlowId());
} catch (Exception e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("JMSFlowManager: INIT error:" + e.getLocalizedMessage());
}
throw e;
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: Initialized");
}
List<FileSystemEvent> fsel = buildEventList(request.getFiles());
if (LOGGER.isInfoEnabled())
LOGGER.info("JMSFlowManager: EventList Built...");
if (consumer.canConsumeAll(fsel)) {
if (LOGGER.isInfoEnabled())
LOGGER.info("JMSFlowManager: Can consume");
// using the Flow manager task executor
Future<Queue<FileSystemEvent>> future = parent.getExecutor().submit(consumer);
// waiting for the result
Queue<FileSystemEvent> result = future.get();
// building the response
List<String> outList = new ArrayList<String>();
for (FileSystemEvent fse : result) {
File f = fse.getSource();
if (f != null)
outList.add(f.getAbsolutePath());
else
outList.add(null);
}
return new JMSFlowResponse(JMSFlowStatus.SUCCESS, outList);
} else {
if (LOGGER.isWarnEnabled())
LOGGER.warn("JMSFlowManager: CanNOT consume");
throw new IllegalArgumentException("JMSFlowManager: Unable to start the flow: "
+ "message do not match flow configuration");
}
}
@InOut
public void process(Exchange exchange) throws Exception {
if (LOGGER.isInfoEnabled())
LOGGER.info("JMSFlowManager: will reply to ->"
+ exchange.getIn().getHeader("JMSReplyTo").toString());
Message in = exchange.getIn();
JMSFlowResponse response = null;
List<String> responses = null;
try {
JMSFlowRequest request = in.getBody(JMSFlowRequest.class);
if (request == null) {
throw new Throwable("Bad message type");
} else {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JMSFlowManager: (" + exchange.getIn().getMessageId()
+ ") STARTING PROCESSING");
}
// do the work
response = workOn(request);
}
} catch (Throwable t) {
if (response != null) {
responses = response.getResponses();
} else {
responses = new ArrayList<String>(1);
}
String message = "JMSFlowManager (" + exchange.getIn().getMessageId() + "): ERROR: "
+ t.getMessage();
responses.add(message);
if (LOGGER.isErrorEnabled()) {
LOGGER.error(message);
}
//TODO // exchange.getIn().setFault(true);
//TODO // exchange.setException(t);
} finally {
// some error occurred in workOn()!
if (response == null) {
if (responses == null)
responses = new ArrayList<String>(1);
String message = "JMSFlowManager (" + exchange.getIn().getMessageId()
+ "): Problem occurred during flow execution";
if (LOGGER.isWarnEnabled())
LOGGER.warn(message);
responses.add(message);
response = new JMSFlowResponse(JMSFlowStatus.FAILURE, responses);
}
in.setBody(response, JMSFlowResponse.class);
// exchange.setOut(in);//TODO check needed?
if (LOGGER.isInfoEnabled())
LOGGER.info("JMSFlowManager: Process ends for message ID:"
+ exchange.getIn().getMessageId());
}
}
/**
* The AsyncProcessor defines a single process() method which is very similar to it's
* synchronous Processor.process() brethren. Here are the differences: A non-null AsyncCallback
* MUST be supplied which will be notified when the exchange processing is completed. It MUST
* not throw any exceptions that occurred while processing the exchange. Any such exceptions
* must be stored on the exchange's Exception property. It MUST know if it will complete the
* processing synchronously or asynchronously. The method will return true if it does complete
* synchronously, otherwise it returns false. When the processor has completed processing the
* exchange, it must call the callback.done(boolean sync) method. The sync parameter MUST match
* the value returned by the process() method.
*
* @author Carlo Cancellieri - carlo.cancellieri@geo-solutions.it
*/
public boolean process(Exchange exchange, AsyncCallback callback) {
try {
process(exchange);
} catch (Throwable t) {
//TODO // exchange.getIn().setFault(true);
//TODO // exchange.setException(t);
} finally {
callback.done(true);
}
return true;
}
public void setDataDirHandler(DataDirHandler dataDirHandler) {
this.dataDirHandler = dataDirHandler;
}
}